package com.wanhe.base.websocket;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import com.google.gson.Gson;
import com.orhanobut.logger.Logger;
import com.wanhe.base.BaseApplication;
import com.wanhe.base.util.EndianUtil;
import com.wanhe.base.util.NumberBytes;
import com.wanhe.base.util.SPUtils;
import com.wanhe.base.websocket.callback.CartStateCallBack;
import com.wanhe.base.websocket.callback.GetTableDetailCallBack;
import com.wanhe.base.websocket.callback.LoginCallBack;
import com.wanhe.base.websocket.callback.OpenTableCallBack;
import com.wanhe.base.websocket.callback.PlaceOrderCallBack;
import com.wanhe.base.websocket.callback.ShopCartCallBack;
import com.wanhe.base.websocket.model.BillDish;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.annotations.NonNull;

public class WebSocketTool {
	private static WebSocketTool webSocketTool;
	String address;
	WebSocketClient webSocketClient;
	//    private WebSocketCallBack callBack;
	WebSocketInitCallBack initCallBack;

	private LoginCallBack loginCallBack;
	private OpenTableCallBack openTableCallBack;
	private CartStateCallBack cartStateCallBack;
	private PlaceOrderCallBack placeOrderCallBack;
	private ShopCartCallBack shopCartCallBack;
	private GetTableDetailCallBack getTableDetailCallBack;

	public static String MSG_SERVER_RETURN = "msgError";
	public static String MSG_RESPONSE = "msg_response";

	public static byte[] bid = new byte[16];
	public List<BaseSocketProtocol> msgList = new ArrayList<>();
	public int currentChannel = 1;

	public void channelAdd(){
		currentChannel++;
	}

	Gson gson = new Gson();
	Handler mHandle;

	private WebSocketTool() {
		mHandle = new Handler(Looper.getMainLooper()) {
			@Override
			public void handleMessage(Message msg) {
				if (msg.what == 0) {
					BaseSocketProtocol protocol = (BaseSocketProtocol) msg.getData().getSerializable(MSG_RESPONSE);
					removdMsg(protocol.flag,protocol.channel);//收到的是回包，去删除相应的信息
					sendBackBag(protocol);//回包
					handleOnMessage(protocol);
				} else if (msg.what == 1) {
					String msgToShow = msg.getData().getString(WebSocketTool.MSG_SERVER_RETURN);
					Toast.makeText(BaseApplication.context, msgToShow, Toast.LENGTH_SHORT).show();
				}
			}
		};
	}

	public static synchronized WebSocketTool getInstance() {
		if (webSocketTool == null) {
			webSocketTool = new WebSocketTool();
			Log.d("websock","WebSocketTool==null");
		}
		return webSocketTool;
	}

	public void setInitCallBack(WebSocketInitCallBack initCallBack) {
		this.initCallBack = initCallBack;
	}

	public interface WebSocketInitCallBack {
		void onOpen(ServerHandshake serverHandshake);

		void onClose(int code, String reason, boolean remote);
	}

	public void initWebSockClient() throws URISyntaxException {
		if (webSocketClient != null && webSocketClient.isOpen()) {
			Log.d("webSocket", "isOpen=" + webSocketClient.isOpen());
			return;
		}
		address = "ws://" + SPUtils.getIp() + ":" + "8400";
		Log.d("webSocket", "address=" + address);
		webSocketClient = new WebSocketClient(new URI(address)) {
			@Override
			public void onOpen(ServerHandshake serverHandshake) {
				Log.i("client", "onOpen");
				if (initCallBack != null)
					initCallBack.onOpen(serverHandshake);
			}

			@Override
			public void onMessage(String message) {
				Log.i("client", "message:" + message);
			}

			@Override
			public void onClose(int code, String reason, boolean remote) {
				Log.i("Client", "onClose");
				closeConnect();
				if (initCallBack != null)
					initCallBack.onClose(code, reason, remote);

			}

			@Override
			public void onError(final Exception ex) {
				Log.i("Client", "ex=" + ex.getMessage());
				Message message = mHandle.obtainMessage();
				message.what = 1;
				Bundle bundle = new Bundle();
				bundle.putString(MSG_SERVER_RETURN, ex.getMessage());
				message.setData(bundle);
				message.sendToTarget();
			}

			@Override
			public void onMessage(ByteBuffer bytes) {
				super.onMessage(bytes);
			    BaseSocketProtocol protocol = encodeResponse(bytes);
				//				handleMessage(protocol);
				Message message = mHandle.obtainMessage();
				message.what = 0;
				Bundle bundle = new Bundle();
				bundle.putSerializable(MSG_RESPONSE, protocol);
				message.setData(bundle);
				message.sendToTarget();
				Log.i("client", "message:body=" + protocol.body);
				Log.i("client", "message:digest=" + protocol.digest);
				//                if (callBackMap != null && callBackMap.size() > 0) {
				//                    for (Map.Entry entry : callBackMap.entrySet()) {
				//                        ((WebSocketCallBack) entry.getValue()).onMessage((String) entry.getKey(), protocol);
				//                    }
				//                }
			}
		};
	}

	private void handleOnMessage(BaseSocketProtocol protocol) {
		if (protocol != null) {
			if (protocol.commonId == 4007) {//登录
				if (loginCallBack != null)
					loginCallBack.onlogin(protocol.body);
			} else if (protocol.commonId == 4003) {//开台
				if (openTableCallBack != null)
					openTableCallBack.onOpenTable(protocol.body,  protocol.source);
			} else if (protocol.commonId == 1201) {//购物车状态
				if (cartStateCallBack != null)
					cartStateCallBack.onCartState(protocol.body);
			} else if (protocol.commonId == 1217) {//操作购物车
				if (shopCartCallBack != null)
					shopCartCallBack.onCartChange(protocol.body, protocol.digest);
			} else if (protocol.commonId == 1205) {//下单
				if (placeOrderCallBack != null)
					placeOrderCallBack.placeOrderRespon(protocol.body);
			} else if (protocol.commonId == 4009) {
				if (getTableDetailCallBack != null) {
					getTableDetailCallBack.getTableDetailSuccess(protocol.body);
				}
			}
		}
	}

	public void connect() {
		if (webSocketClient == null) {
			throw new NullPointerException("webSocketClient is null");
		}
		if (webSocketClient.isOpen())
			return;
		new Thread() {
			@Override
			public void run() {
				webSocketClient.connect();
			}
		}.start();
	}


	public void closeConnect() {
		try {
			if (webSocketClient != null) {
				webSocketClient.close();
			} else {
				Log.d("Client", "webSocketClient==null");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			webSocketClient = null;
		}
	}


	public void sendMsg(byte[] msg) {
		if (!webSocketClient.isOpen())
			return;
		if (webSocketClient != null) {
			webSocketClient.send(msg);
		}
	}

	/**
	 * 登录
	 * @param tag
	 * @param userName
	 * @param password
	 * @param machineCode
	 * @param callBack
	 */
	public void padLogin(int tag, String userName, String password, String machineCode, LoginCallBack callBack) {
		if (callBack != null)
			this.loginCallBack = callBack;
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.digest = tag;
		protocol.commonId = 4006;
		protocol.cmdType = 3;
		protocol.flag = 0;
		protocol.channel = currentChannel;
		Map<String, String> map = new HashMap<>();
		map.put("userName", userName);
		map.put("password", password);
		map.put("machineCode", machineCode);
		protocol.body = gson.toJson(map);
		Log.i("body", "gson.toJson(gson.toJson(map))=" + gson.toJson(gson.toJson(map)));

		try {
			sendMsg(sendMessage(protocol));
			addMsg(protocol);
			currentChannel++;
			//            sendMessage(protocol);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 开桌
	 * @param tag
	 * @param guid
	 * @param guestNum
	 * @param openTableCallBack
	 */
	public void openTable(int tag, String guid, int guestNum, OpenTableCallBack openTableCallBack) {
		if (openTableCallBack != null)
			this.openTableCallBack = openTableCallBack;
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.commonId = 4002;
		protocol.digest = tag;
		protocol.flag = 0;
		protocol.cmdType = 3;
		protocol.channel = currentChannel;
//		Map<String, Object> bodyMap = new HashMap<>();
		Map<String, Object> map = new HashMap<>();
		map.put("opType", 0);
		map.put("tableUuid", guid);
		map.put("userName", SPUtils.getAccount());
		map.put("password", SPUtils.getPassword());
		map.put("guestNum", guestNum);
//		map.put("json", gson.toJson(bodyMap));
		protocol.body = gson.toJson(map);
		Log.i("body", "gson.toJson(gson.toJson(map))=" +  gson.toJson(map));
		try {
			byte[] sendData = sendMessage(protocol);
			sendMsg(sendData);
			addMsg(protocol);
			currentChannel++;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 购物车状态
	 */
	public void syncCartState() {
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.commonId = 1200;
		protocol.flag = 0;
		protocol.channel = currentChannel;
		protocol.cmdType = 3;
		protocol.digest = (int) System.currentTimeMillis();
		if(!TextUtils.isEmpty(SPUtils.getKeySource())){
			protocol.source = Short.parseShort(SPUtils.getKeySource());
		}
		try {
			sendMsg(sendMessage(protocol));
			addMsg(protocol);
			currentChannel++;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}


	/**
	 * 添加和删除菜品
	 *
	 * @param billDish
	 */
	public void shopCart(BillDish billDish) {
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.commonId = 1216;
		protocol.digest = (int) System.currentTimeMillis();
		protocol.channel = currentChannel;
		protocol.cmdType = 3;
		if (!TextUtils.isEmpty(SPUtils.getKeySource())){
			protocol.source = Short.parseShort(SPUtils.getKeySource());
		}
		Map<String, Object> map = new HashMap<>();
		map.put("opType", 1);//目前就直接写死为1
		map.put("cartJson", gson.toJson(billDish));
		protocol.body = gson.toJson(map);
		Logger.d("====shopCart====" + gson.toJson(map));
		try {
			sendMsg(sendMessage(protocol));
			addMsg(protocol);
			currentChannel++;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 下单
	 *
	 * @param peopleNum
	 * @param account
	 * @param pwd
	 */
	public void placeOrder(String peopleNum, String account, String pwd) {
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.commonId = 1204;
		protocol.digest = (int) System.currentTimeMillis();
		protocol.channel = currentChannel;
		protocol.flag = 0;
		protocol.cmdType = 3;
//		if (!TextUtils.isEmpty(SPUtils.getBid())){
//			protocol.bid = SPUtils.getBid();
//		}
//		if (!TextUtils.isEmpty(SPUtils.getKeySource())){
//			protocol.source = Short.parseShort(SPUtils.getKeySource());
//		}
		Map<String, Object> map = new HashMap<>();
		map.put("peopleNum", peopleNum);
		map.put("waiterUser", account);
		map.put("waiterPassword", pwd);
		protocol.body = gson.toJson(map);
		Logger.d("====placeOrder====" + gson.toJson(map));
		try {
			sendMsg(sendMessage(protocol));
			addMsg(protocol);
			currentChannel++;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 餐桌信息
	 * @param tableUuid
	 * @param callBack
	 */
	public void getTableDetail(String tableUuid, GetTableDetailCallBack callBack) {
		if (callBack != null)
			this.getTableDetailCallBack = callBack;
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.commonId = 4008;
		protocol.digest = (int) System.currentTimeMillis();
		protocol.channel = currentChannel;
		protocol.flag = 0;
		protocol.cmdType = 3;
		//        if(!TextUtils.isEmpty(SPUtils.getBid()))protocol.bid = SPUtils.getBid();
		//        if(!TextUtils.isEmpty(SPUtils.getKeySource())) protocol.source = Short.parseShort(SPUtils.getKeySource());
		Map<String, Object> map = new HashMap<>();
		map.put("tableUuid", tableUuid);
		map.put("opType", 0);
		protocol.body = gson.toJson(map);
		Log.i("body", "gson.toJson(map)=" + gson.toJson(gson.toJson(map)));
		try {
			sendMsg(sendMessage(protocol));
			addMsg(protocol);
			currentChannel++;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}


	private void handleResend(BaseSocketProtocol protocol){
       if(protocol.commonId == 4901){//重新连接

	   }
	}
	/**
	 * 心跳包
	 * @return
	 */
	public Flowable sendHeartbeat(){
		return Flowable.create(new FlowableOnSubscribe<String>() {
			@Override
			public void subscribe(@NonNull FlowableEmitter<String> e) throws Exception {
				while (true){
					try {
						Thread.sleep(2000);
					} catch (InterruptedException et) {
						et.printStackTrace();
						e.onError(et);
					}
					BaseSocketProtocol baseSocketProtocol = new BaseSocketProtocol();
					baseSocketProtocol.flag = 2;
					baseSocketProtocol.commonId=10000;
					try {
						sendMsg(sendMessage(baseSocketProtocol));
					} catch (UnsupportedEncodingException ex) {
						ex.printStackTrace();
						e.onError(ex);
					}
					e.onNext(null);
					e.onComplete();
				}
			}
		}, BackpressureStrategy.BUFFER);
	}

	/**
	 * 重新发送
	 */
	public  Flowable resendMsg(){
		return Flowable.create(new FlowableOnSubscribe<String>() {
			@Override
			public void subscribe(@NonNull FlowableEmitter<String> e) throws Exception {
				while (true){
					try {
						Thread.sleep(2000);
					} catch (InterruptedException et) {
						et.printStackTrace();
						e.onError(et);
					}
					if(msgList.size()>0){
						try {
							sendMsg(sendMessage(msgList.get(0)));
						} catch (UnsupportedEncodingException ex) {
							ex.printStackTrace();
						}
					}
					e.onNext(null);
					e.onComplete();
				}
			}
		}, BackpressureStrategy.BUFFER);

	}

	/**
	 * 移除队列中已经发送成功的包
	 * @param Channel
	 */
	public   void removdMsg(int flag,int Channel){
		if(flag==0)return;//flag 等于0 不需要处理
		if(msgList.size()>0){
			//使用java.util.Iterator
			for(Iterator it = msgList.iterator(); it.hasNext();){
				BaseSocketProtocol protocol= (BaseSocketProtocol)it.next();
				if(Channel==protocol.channel){
					synchronized(msgList){
						it.remove();
					}
				}
			}
//			for(BaseSocketProtocol protocol:msgList){
//				if(Channel==protocol.channel){
//					synchronized(msgList){
//						msgList.remove(protocol);
//					}
//				}
//			}
		}
	}

	public void addMsg(BaseSocketProtocol baseSocketProtocol){
		synchronized(msgList){
			msgList.add(baseSocketProtocol);
		}
	}
	/**
	 * 断线重连
	 */
	public void sendReconnection(){
		BaseSocketProtocol protocol = new BaseSocketProtocol();
		protocol.flag =2;
		protocol.commonId = 4900;
		try {
			sendMsg(sendMessage(protocol));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	public void sendBackBag(BaseSocketProtocol protocol){
		if(protocol.flag!=0)return;
		protocol.flag =1;
		try {
			sendMsg(sendMessage(protocol));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	public byte[] sendMessage(BaseSocketProtocol protocol) throws UnsupportedEncodingException {
		if (webSocketClient == null) {
			try {
				initWebSockClient();
				webSocketClient.connect();
			} catch (URISyntaxException e) {
				e.printStackTrace();
			}
		}
		//        if (!webSocketClient.isOpen()) {
		//            webSocketClient.connect();
		//        }
		byte[] totalLen = EndianUtil.littleEndian(NumberBytes.intToBytes(protocol.totalLen));
		byte flag = protocol.flag;
		byte version = protocol.version;
		byte[] cmdType = EndianUtil.littleEndian(NumberBytes.shortToByte(protocol.cmdType));
		byte[] source = EndianUtil.littleEndian(NumberBytes.shortToByte(protocol.source));
		byte[] target = EndianUtil.littleEndian(NumberBytes.shortToByte(protocol.target));
		byte[] commonId = EndianUtil.littleEndian(NumberBytes.intToBytes(EndianUtil.byte2Int(NumberBytes.intToBytes(protocol.commonId))));
		byte[] timestamp = EndianUtil.littleEndian(NumberBytes.longToBytes(protocol.timestamp));
		byte[] digest = EndianUtil.littleEndian(NumberBytes.intToBytes(protocol.digest));
		byte[] bid = WebSocketTool.bid;
		Log.d("websock","bid="+NumberBytes.bytesToInt(bid));
//		byte[] channelByte =  EndianUtil.littleEndian(NumberBytes.intToBytes(currentChannel));
		byte[] channelByte =	EndianUtil.littleEndian(NumberBytes.intToBytes(EndianUtil.byte2Int(NumberBytes.intToBytes(protocol.channel))));
		//        Log.d("webSockeTool","bid="+protocol.bid);
		byte[] body = EndianUtil.littleEndian(protocol.body.getBytes("utf-8"));
		int totalLength = 4 + totalLen.length + 2 + timestamp.length + cmdType.length + source.length + target.length + commonId.length + digest.length + bid.length + body.length+channelByte.length;
		byte[] resultByte = new byte[totalLength];
		Log.i("WebSocketTool", "totalLength=" + totalLength);
		Log.i("WebSocketTool", "body.length=" + body.length);
		totalLen = EndianUtil.littleEndian(NumberBytes.intToBytes(EndianUtil.byte2Int(NumberBytes.intToBytes(totalLength))));
		byte[] bodyLength = EndianUtil.littleEndian(NumberBytes.intToBytes(EndianUtil.byte2Int(NumberBytes.intToBytes(body.length))));
		int index = 0;
		System.arraycopy(totalLen, 0, resultByte, index, totalLen.length);
		index = index + totalLen.length;
		resultByte[index] = flag;
		index = index + 1;
		resultByte[index] = version;
		index = index + 1;
		System.arraycopy(cmdType, 0, resultByte, index, cmdType.length);
		index = index + cmdType.length;
		System.arraycopy(source, 0, resultByte, index, source.length);
		index = index + source.length;
		System.arraycopy(target, 0, resultByte, index, target.length);
		index = index + target.length;
		System.arraycopy(commonId, 0, resultByte, index, commonId.length);
		index = index + commonId.length;
		System.arraycopy(timestamp, 0, resultByte, index, timestamp.length);
		index = index + timestamp.length;
		System.arraycopy(digest, 0, resultByte, index, digest.length);
		index = index + digest.length;
		System.arraycopy(bid, 0, resultByte, index, bid.length);
		index = index + bid.length;
		System.arraycopy(channelByte, 0, resultByte, index, channelByte.length);
		index = index + channelByte.length;
		System.arraycopy(bodyLength, 0, resultByte, index, bodyLength.length);
		index = index + bodyLength.length;
		System.arraycopy(body, 0, resultByte, index, body.length);
		Log.i("WebSocketTool", "resultByte=" + resultByte.length + new String(resultByte, "utf-8"));
		return resultByte;
	}


	public BaseSocketProtocol encodeResponse(ByteBuffer bytes) {
		BaseSocketProtocol baseSocketProtocol = new BaseSocketProtocol();
		baseSocketProtocol.totalLen = encodeInt(bytes, 0);
		baseSocketProtocol.digest = encodeInt(bytes, 24);
		baseSocketProtocol.flag = bytes.get(4);
		baseSocketProtocol.version = bytes.get(5);


		baseSocketProtocol.commonId = encodeInt(bytes, 12);
		baseSocketProtocol.cmdType = encodeShort(bytes, 6);
		baseSocketProtocol.source = encodeShort(bytes, 8);
		baseSocketProtocol.target = encodeShort(bytes, 10);
		baseSocketProtocol.channel = encodeInt(bytes,44);
		int bodyLenght = encodeInt(bytes,48);
		if(baseSocketProtocol.commonId==4003&&baseSocketProtocol.flag == 0){
			this.bid = getUUID2(bytes);
		}
		if(bodyLenght!=0){
			baseSocketProtocol.body = encodeBody(bytes, 52);
		}
		return baseSocketProtocol;
	}


	private int encodeInt(ByteBuffer bytes, int startIndex) {
		byte[] data = bytes.array();
		byte[] digestByte = new byte[4];
		System.arraycopy(data, startIndex, digestByte, 0, digestByte.length);
		int digest = NumberBytes.bytesToInt(digestByte);
		return digest;
	}

	private short encodeShort(ByteBuffer bytes, int startIndex) {
		byte[] data = bytes.array();
		byte[] iShort = new byte[2];
		System.arraycopy(data, startIndex, iShort, 0, iShort.length);
		short digest = NumberBytes.byteToShort(iShort);
		return digest;
	}

	private String encodeBody(ByteBuffer bytes, int startIndex) {
		byte[] data = bytes.array();
		byte[] body = new byte[bytes.array().length - 52];
		System.arraycopy(data, startIndex, body, 0, body.length);
		String sBody = null;
		try {
			sBody = new String(body, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return sBody;
	}

	private String getUUID(ByteBuffer bytes) {
		byte[] data = bytes.array();
		byte[] uuid = new byte[16];
		System.arraycopy(data, 28, uuid, 0, uuid.length);
		return UUID.nameUUIDFromBytes(uuid).toString();
	}

	private byte[] getUUID2(ByteBuffer bytes) {
		byte[] data = bytes.array();
		byte[] uuid = new byte[16];
		System.arraycopy(data, 28, uuid, 0, uuid.length);
		return uuid;
	}

	public void setShopCartCallBack(ShopCartCallBack shopCartCallBack){
		this.shopCartCallBack = shopCartCallBack;
	}

	public void setPlaceOrderCallBack(PlaceOrderCallBack placeOrderCallBack){
		this.placeOrderCallBack = placeOrderCallBack;
	}

	public void setCartStateCallBack(CartStateCallBack cartStateCallBack){
		this.cartStateCallBack = cartStateCallBack;
	}

}
